#ifndef BACKPACK_GAMELOOP_H_
#define BACKPACK_GAMELOOP_H_

#include "GameContext.hpp"
#include "Inventory.hpp"
#include "Action.hpp"
#include "HotSpot.hpp"
#include "HotSpotFilter.hpp"
#include "View.hpp"
#include "UserActivity.hpp"
#include "UserActivityFactory.hpp"
#include "lambda_visitor.hpp"

#include <cassert>
#include <boost/utility.hpp>

#define MSG_YOU_ARE "You are "
#define MSG_YOU_SEE " you see "

#define MSG_OPEN_INVENTORY "open inventory"
#define MSG_QUIT_GAME "quit game"


namespace backpack {



template <class View>
class GameLoop : public boost::static_visitor<>, boost::noncopyable {
public:
  GameLoop(ExtendedGameContext &context, Inventory &inventory, View &view)
    : m_rContext(context), m_rInventory(inventory), m_rView(view),
    m_pLastNode(nullptr), m_bGameOver(false) {
  }
  void loop(GameNode const&);

private:
  ExtendedGameContext &m_rContext;
  Inventory &m_rInventory;
  View &m_rView;
  GameNode const* m_pLastNode;
  bool m_bGameOver;

  class ActionExecutor : public boost::static_visitor<> {
  public:
    ActionExecutor(GameContext &context, View &view, bool &gameOver)
      : m_rContext(context), m_rView(view), m_bGameOver(gameOver) {
    }
    template <class SingleAction>
    void operator() (SingleAction &action) {
      action.perform(m_rView, m_rContext);
    }
    void operator() (MultiAction &multiAction) {
      multiAction.accept(*this);
    }
    void operator() (EndAction &) {
      m_bGameOver = true;
    }
  private:
    GameContext &m_rContext;
    View &m_rView;
    bool &m_bGameOver;
  }; // struct ActionExecutor

  void addHotSpotDescriptions(NodeDescriptionBuilder<View> &) const;
  void addHotSpotOptions(UserChoiceBuilder<View> &) const;
}; // class GameLoop




template <class View>
void GameLoop<View>::loop(GameNode const& startNode) {
  m_rContext.enqueueAction(MoveAction(startNode));

  ActionExecutor gameLogicUpdater(m_rContext, m_rView, m_bGameOver);

  while (true) {
    // update game logic
    m_rContext.processActionQueue(gameLogicUpdater);
    if (m_bGameOver) {
      break;
    }

    // if node changed, show new description
    GameNode const& currentNode = m_rContext.getCurrentNode();
    if (m_pLastNode != &currentNode) {
      NodeDescriptionBuilder<View> descBuilder = m_rView.newNodeDescription();
      descBuilder.setNodeDescription(MSG_YOU_ARE
          + currentNode.getLocationString() +" "
          + currentNode.getDescription());
      addHotSpotDescriptions(descBuilder);
      descBuilder.waitForPlayerConfirmation();
    }
    m_pLastNode = &currentNode;

    // let player choose activity
    UserChoiceBuilder<View> choiceBuilder = m_rView.newUserChoice();
    choiceBuilder.setTitle(currentNode.getName());
    addHotSpotOptions(choiceBuilder);
//    choiceBuilder.addOption('i', MSG_OPEN_INVENTORY, InventoryActivity());
    choiceBuilder.addOption('q', MSG_QUIT_GAME, QuitActivity());

    UserActivityVariant const& chosen = choiceBuilder.letPlayerChoose();

    // add players choice to queue
    m_rContext.enqueueAction(UserActivityAction(chosen));
  }
}



template <class View>
void GameLoop<View>::addHotSpotDescriptions(
    NodeDescriptionBuilder<View> & builder) const {

  auto visitor = filterInvisible(util::make_lambda_visitor(
      [&builder] (WayPoint const& wayPoint) {
        builder.addWayPointDescription(wayPoint.getLocationString()
          + MSG_YOU_SEE + wayPoint.getDescription());
      },
      [&builder] (TouchPoint const& touchPoint) {
        builder.addTouchPointDescription(touchPoint.getLocationString()
          + MSG_YOU_SEE + touchPoint.getDescription());
      },
      [&builder] (UnlockPoint const& unlockPoint) {
        builder.addUnlockPointDescription(unlockPoint.getLocationString()
          + MSG_YOU_SEE + unlockPoint.getDescription());
      }));

  m_rContext.exploreCurrentNode(visitor);
}



template <class View>
void GameLoop<View>::addHotSpotOptions(
    UserChoiceBuilder<View> &builder) const {

  ChainOfResponsibilityUserActivityFactory createUserActivity;

  auto visitor = filterInvisible(util::make_lambda_visitor(
      [&] (WayPoint &wayPoint) {
        builder.addOption(
          wayPoint.getChoiceString() + " " + wayPoint.getName(),
          createUserActivity(wayPoint)
          );
      },
      [&] (TouchPoint &touchPoint)  {
        builder.addOption(
          touchPoint.getChoiceString() + " " + touchPoint.getName(),
          createUserActivity(touchPoint)
          );
      },
      [&] (UnlockPoint &unlockPoint) {
        builder.addOption(
          unlockPoint.getChoiceString() + " " + unlockPoint.getName(),
          createUserActivity(unlockPoint)
          );
      }));

  m_rContext.exploreCurrentNode(visitor);
}






} // namespace backpack

#endif // BACKPACK_GAMELOOP_H_

